﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace PhotovoltaicETL
{
    /// <summary>
    /// ETL计算类。当前只支持常数和本机3000F取值。代码没有预留其他驱动的使用方法。其他的驱动方法增加时需要修改本类部分结构
    /// </summary>
    public class DataETL
    {
        List<string> resSql = new List<string>();
        //int temp = 0;
        /// <summary>
        /// 单例实例
        /// </summary>
        static DataETL context;
        /// <summary>
        /// 模拟量点计算周期。初始化时赋值
        /// </summary>
        int aiCalRate;
        /// <summary>
        /// 电度量计算周期。初始化时赋值
        /// </summary>
        int accCalRate;
        /// <summary>
        /// 模拟量点删除周期。初始化时赋值
        /// </summary>
        int aiDelRate;
        /// <summary>
        /// 电度量点删除周期。初始化时赋值
        /// </summary>
        int accDelRate;
        /// <summary>
        /// 暂存模拟量对应的类型ID。初始化时赋值
        /// </summary>
        sbyte aiType;
        /// <summary>
        /// 暂存电度量对应的类型ID。初始化时赋值
        /// </summary>
        sbyte accType;
        /// <summary>
        /// 模拟量计数器。每增加一次表示过去一分钟
        /// </summary>
        //int aiTimeCount = 0;
        /// <summary>
        /// 电度量计数器。每增加一次表示过去一分钟
        /// </summary>
        //int accTimeCount = 0;
        /// <summary>
        /// 用于记录是否初始化成功。初始化成功后置为true
        /// </summary>
        bool hasInit = false;
        /// <summary>
        /// 本类的单例初始化获取方法
        /// </summary>
        Dictionary<string, 隐藏变量表> aiDisables = null;
        Dictionary<string, 隐藏变量表> accDisables = null;
        List<计算量配置表> aiconfigs = null;
        List<计算量配置表> accconfigs = null;
        Dictionary<string, List<计算群组表>> pointListByGroup = null;
        public static DataETL Context
        {
            get
            {
                if (context == null)
                    context = new DataETL();
                return context;
            }
        }
        /// <summary>
        /// 从数据库初始化系统参数（私有变量）。由于每次ETL时会检查初始化状态，因此不用做为public
        /// </summary>
        private void InitParam()
        {
            try
            {
                using (etldbEntities entity = new etldbEntities())
                {
                    aiType = (from c in entity.点类型表 where c.类型描述 == "遥测" select c).First().类型ID;
                    accType = (from c in entity.点类型表 where c.类型描述 == "遥脉" select c).First().类型ID;
                    var aiConfig = (from c in entity.计算周期配置表 where c.点类型 == aiType select c).First();
                    aiCalRate = aiConfig.计算周期;
                    aiDelRate = aiConfig.删除周期;
                    var accConfig = (from c in entity.计算周期配置表 where c.点类型 == accType select c).First();
                    accCalRate = accConfig.计算周期;
                    accDelRate = accConfig.删除周期;
                    int curminute = (int)Math.Floor((DateTime.UtcNow - DateTime.UtcNow.Date).TotalMinutes);
                    //aiTimeCount = curminute % aiCalRate - 1;
                    //accTimeCount = curminute % accCalRate - 1;
                    aiDisables = (from c in entity.隐藏变量表 where c.pointType == aiType select c).ToDictionary((s) => s.pointName);
                    accDisables = (from c in entity.隐藏变量表 where c.pointType == accType select c).ToDictionary((s) => s.pointName);
                    aiconfigs = (from c in entity.计算量配置表 where c.点类型 == aiType select c).ToList();
                    accconfigs = (from c in entity.计算量配置表 where c.点类型 == accType select c).ToList();
                    Debug.WriteLine("Start with : " + DateTime.Now);
                    pointListByGroup = new Dictionary<string, List<计算群组表>>();
                    foreach (var c in entity.计算群组表.GroupBy(t => t.组名))
                    {
                        pointListByGroup.Add(c.Key, c.ToList());
                    }
                    hasInit = true;
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                hasInit = false;
            }
        }
        /// <summary>
        /// 进行ETL工作过程，检查初始化状态，已经初始化后进行计算
        /// </summary>
        public void DoETL()
        {
            if (!hasInit)
            {
                InitParam();
            }
            if (hasInit)
            {
                Calc();
            }
        }
        /// <summary>
        /// 计算所有变量值
        /// </summary>
        private void Calc()
        {
            Dictionary<string, 模拟量值表> anaList = new Dictionary<string, 模拟量值表>();
            Dictionary<string, 电度值表> accList = new Dictionary<string, 电度值表>();
            //Debug.WriteLine("  | aiCalRate = " + aiCalRate + "  accCalRate = " + accCalRate);

            DateTime nowTime = DateTime.UtcNow.AddSeconds(0 - DateTime.UtcNow.Second);
            int aiTimeCount = nowTime.Minute % aiCalRate;
            int accTimeCount = nowTime.Minute % accCalRate;
            Debug.WriteLine("Arrived with : " + DateTime.Now + "  | aiCount = " + aiTimeCount + "  accCount = " + accTimeCount);
            if (aiTimeCount == 0 || accTimeCount == 0)
            {
                try
                {
                    using (xopensdbEntities xopens = new xopensdbEntities())
                    {
                        anaList = xopens.模拟量值表.ToDictionary(s => s.代码);
                        accList = xopens.电度值表.ToDictionary(s => s.代码);

                        //Debug.WriteLine("01");
                    }
                }
                catch //(Exception ex)
                {
                    Debug.WriteLine("xopens库取值出错！");
                    //aiTimeCount = aiTimeCount >= aiCalRate ? 0 : aiTimeCount;
                    //accTimeCount = accTimeCount >= accCalRate ? 0 : accTimeCount;
                    return;
                }
            }
            if (aiTimeCount == 0)
            {
                try
                {
                    CalcAI(anaList, accList, nowTime);
                    //Debug.WriteLine("02");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    //return;
                }
                //aiTimeCount = 0;
            }
            if (accTimeCount == 0)
            {
                try
                {
                    CalcACC(anaList, accList, nowTime);
                    //Debug.WriteLine("03");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    //return;
                }
                //accTimeCount = 0;
            }
            if (aiTimeCount == 0 || accTimeCount == 0)
            {
                try
                {
                    UpdataDB(nowTime);
                    //Debug.WriteLine("04");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                }
            }
        }
        /// <summary>
        /// 检测组装的sql语句长度
        /// </summary>
        /// <param name="count">计数器</param>
        /// <param name="sqlBuilder">组装的sql</param>
        /// <returns>返回的计数器</returns>
        private int CheckSQLLenth(int count, StringBuilder sqlBuilder)
        {
            count++;
            if (count >= 300)
            {
                resSql.Add(sqlBuilder.ToString());
                sqlBuilder.Clear();
                count = 0;
            }
            return count;
        }
        /// <summary>
        /// 计算所有模拟量。先复制值字典，再计算变量
        /// </summary>
        /// <param name="anaList">模拟量在SCADA中的值字典</param>
        /// <param name="accList">电度量在SCADA中的值字典</param>
        /// <param name="now">当前时间</param>
        private void CalcAI(Dictionary<string, 模拟量值表> anaList, Dictionary<string, 电度值表> accList, DateTime now)
        {
            int sqlCount = 0;
            StringBuilder sqlBuilder = new StringBuilder();
            foreach (var item in anaList.Values)
            {
                if (!aiDisables.Keys.Contains(item.代码))
                {
                    //注意修改取值字段
                    sqlBuilder.AppendLine("INSERT INTO 计算存储表 VALUES ('" + item.代码 + "', '" + now.ToString() + "'," + aiType + "," + (item.值 == null ? 0 : (double)item.值) + ");");
                    sqlCount = CheckSQLLenth(sqlCount, sqlBuilder);
                }
            }
            foreach (var item in aiconfigs)
            {
                //调用计算类针对计算类型进行计算
                double cValue = PointCalc.Calc(item.计算类型表.类型描述, GetListValueFromGroup(item.计算源1组, anaList, accList), GetListValueFromGroup(item.计算源2组, anaList, accList));
                sqlBuilder.AppendLine("INSERT INTO 计算存储表 VALUES ('" + item.目标点名 + "', '" + now.ToString() + "'," + aiType + "," + cValue + ");");
                sqlCount = CheckSQLLenth(sqlCount, sqlBuilder);
            }
            if (sqlBuilder != null && sqlBuilder.Length > 0)
                resSql.Add(sqlBuilder.ToString());
            sqlBuilder.Clear();
        }

        /// <summary>
        /// 计算所有电度量。先复制值字典，再计算变量
        /// </summary>
        /// <param name="anaList">模拟量在SCADA中的值字典</param>
        /// <param name="accList">电度量在SCADA中的值字典</param>
        /// <param name="now">当前时间</param>
        private void CalcACC(Dictionary<string, 模拟量值表> anaList, Dictionary<string, 电度值表> accList, DateTime now)
        {
            int sqlCount = 0;
            StringBuilder sqlBuilder = new StringBuilder();
            foreach (var item in accList.Values)
            {
                if (!accDisables.Keys.Contains(item.代码))
                {
                    //注意修改取值字段
                    sqlBuilder.AppendLine("INSERT INTO 计算存储表 VALUES ('" + item.代码 + "', '" + now.ToString() + "'," + accType + "," + (item.当前表码 == null ? 0 : (double)item.当前表码) + ");");
                    sqlCount = CheckSQLLenth(sqlCount, sqlBuilder);
                }
            }

            foreach (var item in accconfigs)
            {
                //调用计算类针对计算类型进行计算
                double cValue = PointCalc.Calc(item.计算类型表.类型描述, GetListValueFromGroup(item.计算源1组, anaList, accList), GetListValueFromGroup(item.计算源2组, anaList, accList));
                sqlBuilder.AppendLine("INSERT INTO 计算存储表 VALUES ('" + item.目标点名 + "', '" + now.ToString() + "'," + accType + "," + cValue + ");");
                sqlCount = CheckSQLLenth(sqlCount, sqlBuilder);
            }
            if (sqlBuilder != null && sqlBuilder.Length > 0)
                resSql.Add(sqlBuilder.ToString());
            sqlBuilder.Clear();
        }
        /// <summary>
        /// 针对一个计算群组进行取值
        /// </summary>
        /// <param name="groupName">组名称</param>
        /// <param name="anaList">模拟量在SCADA中的值字典</param>
        /// <param name="accList">电度量在SCADA中的值字典</param>
        /// <returns>组中对应所有点的值</returns>
        private List<double> GetListValueFromGroup(string groupName, Dictionary<string, 模拟量值表> anaList, Dictionary<string, 电度值表> accList)
        {
            List<计算群组表> pointList = pointListByGroup[groupName];
            List<double> valueList = new List<double>();
            foreach (var point in pointList)
            {
                if (point.取值类型表.类型描述 == "system")
                {
                    if (point.取值类型表.驱动信息 == "常数")
                    {
                        valueList.Add(point.基数);
                    }
                    else
                    {
                        // TODO:其他取值类型暂不实现
                    }
                }
                //目前针对3000F驱动暂时没有取驱动方式进行连接。统一采用本机连接增加效率
                else if (point.取值类型表.类型描述 == "3000F")
                {
                    if (point.点类型 == aiType)
                    {
                        模拟量值表 tempValue = null;
                        if (anaList.TryGetValue(point.点名, out tempValue))
                        {
                            //注意修改取值字段
                            double value = tempValue.值 == null ? 0 : (double)tempValue.值;
                            value = value * point.系数 + point.基数;
                            valueList.Add(value);
                        }
                        else
                        {
                            valueList.Add(0);
                        }
                    }
                    else if (point.点类型 == accType)
                    {
                        电度值表 tempValue = null;
                        if (accList.TryGetValue(point.点名, out tempValue))
                        {
                            //注意修改取值字段
                            double value = tempValue.当前表码 == null ? 0 : (double)tempValue.当前表码;
                            value = value * point.系数 + point.基数;
                            valueList.Add(value);
                        }
                        else
                        {
                            valueList.Add(double.MinValue);
                        }
                    }
                }
            }
            return valueList;
        }
        /// <summary>
        /// 更新数据库
        /// </summary>
        private void UpdataDB(DateTime now)
        {
            DateTime nowAiRev = now.AddMinutes(0 - aiDelRate);
            DateTime nowAccRev = now.AddMinutes(0 - accDelRate);
            try
            {
                //Debug.WriteLine(resSql.Count);
                StringBuilder tempsql = new StringBuilder();
                int count = 0;
                using (etldbEntities entity = new etldbEntities())
                {
                    foreach (var sql in resSql)
                    {
                        tempsql.Append(sql);
                        if (count++ >= 10)
                        {
                            string res = tempsql.ToString();
                            tempsql.Clear();
                            if (res != null && (!string.IsNullOrWhiteSpace(res)))
                                entity.Database.ExecuteSqlCommand(res);
                        }
                    }
                    string resf = tempsql.ToString();
                    if (resf != null && (!string.IsNullOrWhiteSpace(resf)))
                        entity.Database.ExecuteSqlCommand(resf);
                    resSql.Clear();
                    entity.Database.ExecuteSqlCommand("DELETE FROM 计算存储表 " +
                        "WHERE (点类型=" + aiType + " AND 时间<'" + nowAiRev + "')" +
                        " OR (点类型 = " + accType + " AND 时间 < '" + nowAccRev + "')");
                    entity.Database.ExecuteSqlCommand("INSERT INTO etl完成记录 VALUES ('" + now + "','" + DateTime.Now + "')");
                    entity.Database.ExecuteSqlCommand("DELETE FROM etl完成记录 " +
                        "WHERE 数据时间<'" + now.AddHours(-6) + "'");
                }
            }
            catch (Exception ex)
            {
                resSql.Clear();
                Debug.WriteLine(now.ToString("HH-mm-ss:fff") + ":" + ex);
            }
        }
    }
}
